package com.tang.model.proxy.dynamicproxy.typroxy.proxy;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.Tool;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author tangyuan2meicloud.com
 * @DATE 2021/8/19 6:21
 * @Desc
 * @Remarks
 */
public class TyProxy {
    public static final String ln = "\r\n";

    public static Object newInstance(TyClassLoader loader,
                                     Class<?>[] interfaces,
                                     TyInvocationHandler h) {
        try {
//        1.动态生成类文件.java文件
            String src = generatorClassFile(interfaces);
//        2.JAVA文件输出到磁盘
//            File f = writeToFile(src, TyProxy.class.getResource("").getPath(), "$Proxy0.java");
            File f = saveFile(src);

//        3.把生成的.java文件编译成.class文件
            complierClass(f);

            //4、编译生成的.class文件加载到JVM中来
            Class proxyClass = loader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(TyInvocationHandler.class);
            f.delete();

            //5、返回字节码重组以后的新的代理对象
            return c.newInstance(h);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally {
        }
        return null;
    }

    private static void complierClass(File f) throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
        Iterable iterable = manage.getJavaFileObjects(f);

        JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
        task.call();
        manage.close();
    }

    private static File saveFile(String src) throws IOException {
        String filePath = TyProxy.class.getResource("").getPath();
//           System.out.println(filePath);
        File f = new File(filePath + "$Proxy0.java");
        FileWriter fw = new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();
        return f;
    }

    private static String generatorClassFile(Class<?>[] interfaces) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(TyProxy.class.getPackage() + ";" + ln);
        stringBuffer.append("import " + interfaces[0].getName() + ";" + ln);
        stringBuffer.append("import java.lang.reflect.*;" + ln);

        stringBuffer.append("public class $Proxy0 implements " + interfaces[0].getSimpleName() + "{" + ln);
        stringBuffer.append("private TyInvocationHandler handle;" + ln);
        stringBuffer.append("public $Proxy0(TyInvocationHandler handle){" + ln);
        stringBuffer.append("   this.handle = handle;" + ln);
        stringBuffer.append("}" + ln);
        Method[] methods = interfaces[0].getMethods();
        for (Method method : methods) {
            Class<?>[] parameterTypes = method.getParameterTypes();

            stringBuffer.append("public " + method.getReturnType().getName() + " " + method.getName() + " (");
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                stringBuffer.append(parameterType.getName() + " " + toLowCaseClassName(parameterType));
                if (i < parameterTypes.length - 1) {
                    stringBuffer.append(",");
                }
            }
            stringBuffer.append("){" + ln);

            stringBuffer.append(" try" + ln);
            stringBuffer.append("        {" + ln);

            stringBuffer.append("   Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{});" + ln);
            stringBuffer.append("this.handle.invoke(this,m,new Object[]{});" + ln);
            stringBuffer.append("return;");
            stringBuffer.append("        }" + ln);
            stringBuffer.append("        catch(Error _ex) { }" + ln);
            stringBuffer.append("        catch(Throwable throwable)" + ln);
            stringBuffer.append("        {" + ln);
            stringBuffer.append("            throw new UndeclaredThrowableException(throwable);" + ln);
            stringBuffer.append("        }" + ln);
            stringBuffer.append("}" + ln);
        }
        stringBuffer.append("}" + ln);
        return stringBuffer.toString();
    }

    private static String toLowCaseClassName(Class<?> parameterType) {
        String simpleName = parameterType.getSimpleName();
        char firstChar = (char) (simpleName.charAt(0) + 26);
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(firstChar);
        stringBuffer.append(simpleName.substring(1, simpleName.length()));
        return stringBuffer.toString();
    }
}
